using System;
using System.Collections.Generic;
using System.Text;

namespace Sedna.Domain.GenericRange
{
    public abstract class Range<T> : IComparable where T : IComparable
    {
        private T start;
        private T end;                
        
        protected Range(T start, T end)
        {
            this.start = start;
            this.end = end;
        }

        # region Properties

        public T Start
        {
            get { return start; }
        }

        public T End
        {
            get { return end; }
        }

        /// <summary>
        /// Important note: even if Start == End it is not an empty range.
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                if (!IsStartInitialized && !IsEndInitialized)
                    return true;

                if (IsStartInitialized || IsEndInitialized)
                    return false;

                return Compare(Start, End) > 0;
            }
        }
        
        public bool IsClosed
        {
            get
            {
                if (IsEmpty)
                    return false;

                if (!IsStartInitialized || !IsEndInitialized)
                    return false;
                
                return true;
            }
        }                
        
        public bool IsBoundedAbove
        {
            get
            {
                if (IsEmpty)
                    return false;

                return IsEndInitialized;
            }
        }

        public bool IsBoundedBelow
        {
            get
            {
                if (IsEmpty)
                    return false;

                return IsStartInitialized;
            }
        }

        # endregion

        public bool Includes(T value)
        {
            if (IsEmpty)
                return false;
            
            if (IsBoundedBelow && Compare(Start, value) > 0)
                return false;

            if (IsBoundedAbove && Compare(End, value) < 0)
                return false;
            
            return true;            
        }
        
        public bool Includes(Range<T> range)
        {
            if (range == null)
                throw new ArgumentNullException("Range cannot be null");
            
            if (IsEmpty)
                return false;
            
            if (range.IsEmpty)
                return true;
            
            if (!range.IsBoundedBelow && IsBoundedBelow)
                return false;
            
            if (range.IsBoundedBelow && !Includes(range.Start))
                return false;

            if (!range.IsBoundedAbove && IsBoundedAbove)
                return false;

            if (range.IsBoundedAbove && !Includes(range.End))
                return false;
            
            return true;
        }
        
        public bool Overlaps(Range<T> range)
        {
            if (range == null)
                throw new ArgumentNullException("Range cannot be null");
            
            if (IsEmpty || range.IsEmpty)
                return true;
            
            if (!IsBoundedBelow && !range.IsBoundedBelow)
                return true;

            if (!IsBoundedAbove && !range.IsBoundedAbove)
                return true;
            
            return range.Includes(Start) || range.Includes(End) || Includes(range);
        }
        
        public Range<T> Gap(Range<T> range)
        {
            if (range == null)
                throw new ArgumentNullException("Range cannot be null");
            
            if (Overlaps(range)) return EvalEmpty;
            
            Range<T> lower, higher;

            if (CompareTo(range) < 0)
            {
                lower = this;
                higher = range;
            }
            else
            {
                lower = range;
                higher = this;
            }

            return EvalGap(lower, higher);
        }

        public bool Abuts(Range<T> range)
        {
            if (range == null)
                throw new ArgumentNullException("Range cannot be null");
            
            return !Overlaps(range) && Gap(range).IsEmpty;
        }

        public override int GetHashCode()
        {
            if (IsEmpty)
                return -1.GetHashCode();

            if (IsBoundedBelow)
                Start.GetHashCode();

            return End.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(this, obj))
                return true;

            Range<T> that = (Range<T>)obj;

            if (that == null || !IsTypeValid(that))
                return false;

            if (IsEmpty && that.IsEmpty)
                return true;
            
            if (IsBoundedBelow && !that.IsBoundedBelow)
                return false;

            if (!IsBoundedBelow && that.IsBoundedBelow)
                return false;

            if (IsBoundedBelow && that.IsBoundedBelow && Compare(Start, that.Start) != 0)
                return false;

            if (IsBoundedAbove && !that.IsBoundedAbove)
                return false;

            if (!IsBoundedAbove && that.IsBoundedAbove)
                return false;

            if (IsBoundedAbove && that.IsBoundedAbove && Compare(End, that.End) != 0)
                return false; 

            return true;
        }        

        public int CompareTo(object other)
        {
            if (other == null)
            {
                throw new ArgumentNullException("Compared value cannot be null");
            }

            // If they are both empty then they are equal
            if (Equals(other))
                return 0;

            Range<T> that = (Range<T>)other;

            if (that == null || !IsTypeValid(that))
            {
                throw new ArgumentException(String.Format("Compared value has unknown type '{0}'", other.GetType()));
            }

            // Something is always greater than nothing

            if (IsEmpty)
                return -1;

            if (that.IsEmpty)
                return 1;

            if (IsBoundedBelow && !that.IsBoundedBelow)
                return 1;

            if (!IsBoundedBelow && that.IsBoundedBelow)
                return -1;

            if (IsBoundedBelow && that.IsBoundedBelow && !Equals(Start, that.Start))
                return Compare(Start, that.Start);

            if (IsBoundedAbove && !that.IsBoundedAbove)
                return -1;

            if (!IsBoundedAbove && that.IsBoundedAbove)
                return 1;

            if (IsBoundedAbove && that.IsBoundedAbove && !Equals(End, that.End))
                return Compare(End, that.End);

            return 0;
        }

        public override String ToString()
        {
            if (IsEmpty)
                return String.Empty;

            if (IsClosed)
                return String.Format("{0} - {1}", start.ToString(), end.ToString());

            if (IsBoundedAbove)
                return "to " + End.ToString();

            return "from " + Start.ToString();
        }


        protected abstract Range<T> EvalGap(Range<T> lower, Range<T> higher);
        protected abstract Range<T> EvalEmpty { get; }
        
        protected virtual bool Equals(T t1, T t2)
        {
            return t1.Equals(t2);
        }
        
        protected virtual int Compare(T t1, T t2)
        {
            return t1.CompareTo(t2);
        }

        protected abstract bool IsStartInitialized
        {
            get;
        }

        protected abstract bool IsEndInitialized
        {
            get;
        }

        protected abstract bool IsTypeValid(Range<T> range);
    }
}
